Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple converted file sliceorientationpatient #441

Conversation

bpinsard
Copy link
Contributor

Here is a PR to allow scans including variable slicing, such as localizer, to be labelled with acq-<orient> instead of the suffix being indexed (default).

As the different slicing still share a same SeriesID and are split by dcm2niix, I think it can only be done within heudiconv. Correct me if I am wrong.

Another case that doesn't seem to be yet managed is when data are exported with split channels, I might work on a separate PR unless someone feels like it.

@bpinsard
Copy link
Contributor Author

Oups, just saw the #424. It might be worth integrating this feature with #424 .

@codecov
Copy link

codecov bot commented Apr 17, 2020

Codecov Report

Attention: Patch coverage is 87.50000% with 3 lines in your changes missing coverage. Please review.

Project coverage is 81.91%. Comparing base (d798b12) to head (0a05c19).
Report is 226 commits behind head on master.

Files with missing lines Patch % Lines
heudiconv/convert.py 83.33% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #441      +/-   ##
==========================================
+ Coverage   81.88%   81.91%   +0.03%     
==========================================
  Files          41       41              
  Lines        4117     4141      +24     
==========================================
+ Hits         3371     3392      +21     
- Misses        746      749       +3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Member

@yarikoptic yarikoptic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

placed comments along the code. But also we need to figure out how to test it... if any scout suffice (didn't check) -- we already use some in testing, so we could test on those

@@ -611,6 +619,20 @@ def save_converted_files(res, item_dicoms, bids_options, outtype, prefix, outnam
imgtype, "_echo-%d%s" % (echo_number, imgtype)
)
break
# Now check if this run is multi-orientation (v-nav or localizer)
if bids_meta and is_multiorient and 'acq-' not in this_prefix_basename:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that this condition should not check if there is _acq- here but rather below right before modifying the actual prefix -- announcing that because _acq- is there we would not be embedding multi-orientation information which would have been such as estimated.

bids_pairs = this_prefix_basename.split('_')
ses_or_sub_idx = sum([bids_pair.split('-')[0] in ['sub','ses'] for bids_pair in bids_pairs])
bids_pairs.insert(ses_or_sub_idx,'acq-%s'%slice_orient)
this_prefix_basename = '_'.join(bids_pairs)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not immediately clear to me why it is not just adding _acq- at the end and goes through all splitting and then joining. Please

  • add a brief comment what is about to happen (something per sub/session?) and why
  • make it more pep-8 friendly here ( add spaces after , between list entries and function arguments)

re "at the end" -- you added this code AFTER possible addition of suffixes... feels like it should be before, and not interfere with suffixes handling (there is also condition after it which would not add any suffix if any changes to this_prefix_basename happen)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and altogether - it might be worth moving this code block into a function (e.g. , so it could be unittested on its own and also make code not so long right here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed on the separate function following #424 example. Will clean up.
It thought that the BIDS key-value pairs are supposed to be in a specific order, and that _acq- is supposed to go after sub-*[_ses-*] .
As the filenames prefixes are already formed, I found this to be the easiest way to insert it at the proper location. For the other cases that #424 is dealing with (eg. https://github.com/nipy/heudiconv/pull/424/files#diff-a48009f9efe6cc64ffdb74d06e831296R341) , they choose to search through potential key-value pairs that are supposed to be after the inserted key-value, here it does the reverse because there would be too many tags to search for, as it is supposed to be right after sub or ses. Does it make sense?
Will add comment to make it more obvious.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It thought that the BIDS key-value pairs are supposed to be in a specific order, and that _acq- is supposed to go after sub-[_ses-] .

that is correct! but it should go before suffix (like _bold) which comes before extension (.nii.gz).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used this file: https://github.com/bids-standard/pybids/blob/master/bids/layout/config/bids.json as a reference, which lists the following path.

"sub-{subject}[/ses-{session}]/{datatype<anat>|anat}/sub-{subject}[_ses-{session}][_acq-{acquisition}][_ce-{ceagent}][_rec-{reconstruction}]_{suffix<T1w|T2w|T1rho|T1map|T2map|T2star|FLAIR|FLASH|PDmap|PD|PDT2|inplaneT[12]|angio>}.{extension<nii|nii.gz|json>|nii.gz}"

Also in the BIDS specs:
https://bids-specification.readthedocs.io/en/stable/04-modality-specific-files/01-magnetic-resonance-imaging-data.html#anatomy-imaging-data

I am confused, does the order of key-value pairs matter?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes! order matters, sorry -- parts of my comment were misleading and incorrect. The ultimate order prescribed in BIDS itself in the entity table. I think we need to add a function which places a new key in the correct position based on other keys present.
What I was pointing also that it matters for those _key-value pairs to come before _{suffix<T1w...}.{extension<nii...}", thus my original comment:

re "at the end" -- you added this code AFTER possible addition of suffixes... feels like it should be before, and not interfere with suffixes handling (there is also condition after it which would not add any suffix if any changes to this_prefix_basename happen)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I admit that I pretty much adapted the existing cases (which are refactored in #424), so I am basically following the same renaming procedure (even if in their cases they str.replace to insert the tag).
The variable name they used is this_prefix_basename it seems to be only the prefix, but I will be happy to adapt to any change to #424 if necessary.

@bpinsard bpinsard force-pushed the multiple_converted_file_sliceorientationpatient branch from 6c8ea10 to fa7d0ee Compare April 22, 2020 14:01
@bpinsard
Copy link
Contributor Author

bpinsard commented Apr 22, 2020

placed comments along the code. But also we need to figure out how to test it... if any scout suffice (didn't check) -- we already use some in testing, so we could test on those

I looked at the test data and the scout is only one dicom file, would require at least 2 dicom files with differing ImageOrientationPatient, from a localizer for instance. Did you follow a specific procedure to include dicoms in the test set (anonymize, strip data... )?

@bpinsard bpinsard closed this Apr 23, 2020
@bpinsard bpinsard deleted the multiple_converted_file_sliceorientationpatient branch April 23, 2020 19:03
@bpinsard bpinsard restored the multiple_converted_file_sliceorientationpatient branch April 23, 2020 19:04
@bpinsard bpinsard reopened this Apr 23, 2020
@yarikoptic
Copy link
Member

Did you follow a specific procedure to include dicoms in the test set (anonymize, strip data... )?

so you looked at ./heudiconv/tests/data/01-anat-scout which indeed we have a single file.
I wish @mvdoc had annotated in 0cb8fab commit where he got it from so we could add more.

But in the other tests we already use datalad to fetch full blown phantom dataset which would have "good" scouts: see https://github.com/nipy/heudiconv/blob/master/heudiconv/tests/test_regression.py#L28 which fetches http://datasets.datalad.org/?dir=/dbic/QA/ and uses tarball(s) from under http://datasets.datalad.org/?dir=/dbic/QA/sourcedata .

eventually we should add proper fixtures and caching so we don't need to refetch them across tests and tests reruns (would be a nice contribution, if interested, but in a separate PR).

I first thought that you could just add testing to the existing test_conversion in that file using the scout tarball of http://datasets.datalad.org/?dir=/dbic/QA/sourcedata/sub-emmet/ses-20180508/anat (can guess why subject is called emmet? ... but apparently all our scouts are proper 3d volumes in that dataset (never looked at them, thought wrong). But there is a bunch of dicoms we have in http://datasets.datalad.org/?dir=/dicoms/dartmouth-phantoms dataset, so you could craft a similar test which installs ///dicoms/dartmouth-phantoms/ (instead of ///dbic/QA) and uses e.g. bids_test3-20161011/AAHead_Scout_32ch-head-coil_MPR_cor

which has 3 dicoms and dcm2niix produces 3 nii.gz
$> dcm2niix -9 -b y -o . -z y .                               
Chris Rorden's dcm2niiX version v1.0.20200331  (JP2:OpenJPEG) GCC8.3.0 (64-bit Linux)
Found 3 DICOM file(s)
Slices not stacked: orientation varies (vNav or localizer?) [0.991633 -0.0203033 0.127482 0.125499 0.382898 -0.915226] != [0.990337 -0.0649062 0.122559 0.138123 0.382102 -0.91374]
Convert 1 DICOM as ./__AAHead_Scout_32ch-head-coil_20161011084552_4_i00002 (162x162x1x1)
Warning: Check that 2D images are not mirrored.
Convert 1 DICOM as ./__AAHead_Scout_32ch-head-coil_20161011084552_4_i00003 (162x162x1x1)
Warning: Check that 2D images are not mirrored.
Convert 1 DICOM as ./__AAHead_Scout_32ch-head-coil_20161011084552_4_i00001 (162x162x1x1)
Warning: Check that 2D images are not mirrored.
Conversion required 0.052689 seconds (0.052680 for core code).
(dev3) 1 21811.....................................:Thu 23 Apr 2020 05:09:28 PM EDT:.
(git-annex)lena:…-phantoms[master]bids_test3-20161011/AAHead_Scout_32ch-head-coil_MPR_cor
$> ls
1.3.12.2.1107.5.2.43.66112.2016101108475682845701835.dcm
1.3.12.2.1107.5.2.43.66112.2016101108475682864701836.dcm
1.3.12.2.1107.5.2.43.66112.2016101108475682884501837.dcm
__AAHead_Scout_32ch-head-coil_20161011084552_4_i00001.json
__AAHead_Scout_32ch-head-coil_20161011084552_4_i00001.nii.gz
__AAHead_Scout_32ch-head-coil_20161011084552_4_i00002.json
__AAHead_Scout_32ch-head-coil_20161011084552_4_i00002.nii.gz
__AAHead_Scout_32ch-head-coil_20161011084552_4_i00003.json
__AAHead_Scout_32ch-head-coil_20161011084552_4_i00003.nii.gz
(dev3) 1 21812.....................................:Thu 23 Apr 2020 05:09:30 PM EDT:.
(git-annex)lena:…-phantoms[master]bids_test3-20161011/AAHead_Scout_32ch-head-coil_MPR_cor
$> grep Orient *json
__AAHead_Scout_32ch-head-coil_20161011084552_4_i00001.json:	"ImageOrientationPatientDICOM": [
__AAHead_Scout_32ch-head-coil_20161011084552_4_i00002.json:	"ImageOrientationPatientDICOM": [
__AAHead_Scout_32ch-head-coil_20161011084552_4_i00003.json:	"ImageOrientationPatientDICOM": [
(dev3) 1 21813.....................................:Thu 23 Apr 2020 05:12:03 PM EDT:.
(git-annex)lena:…-phantoms[master]bids_test3-20161011/AAHead_Scout_32ch-head-coil_MPR_cor
$> grep -A4 Orient *json
__AAHead_Scout_32ch-head-coil_20161011084552_4_i00001.json:	"ImageOrientationPatientDICOM": [
__AAHead_Scout_32ch-head-coil_20161011084552_4_i00001.json-		0.987256,
__AAHead_Scout_32ch-head-coil_20161011084552_4_i00001.json-		-0.0511309,
__AAHead_Scout_32ch-head-coil_20161011084552_4_i00001.json-		0.150699,
__AAHead_Scout_32ch-head-coil_20161011084552_4_i00001.json-		0.159073,
--
__AAHead_Scout_32ch-head-coil_20161011084552_4_i00002.json:	"ImageOrientationPatientDICOM": [
__AAHead_Scout_32ch-head-coil_20161011084552_4_i00002.json-		0.991633,
__AAHead_Scout_32ch-head-coil_20161011084552_4_i00002.json-		-0.0203033,
__AAHead_Scout_32ch-head-coil_20161011084552_4_i00002.json-		0.127482,
__AAHead_Scout_32ch-head-coil_20161011084552_4_i00002.json-		0.125499,
--
__AAHead_Scout_32ch-head-coil_20161011084552_4_i00003.json:	"ImageOrientationPatientDICOM": [
__AAHead_Scout_32ch-head-coil_20161011084552_4_i00003.json-		0.990337,
__AAHead_Scout_32ch-head-coil_20161011084552_4_i00003.json-		-0.0649062,
__AAHead_Scout_32ch-head-coil_20161011084552_4_i00003.json-		0.122559,
__AAHead_Scout_32ch-head-coil_20161011084552_4_i00003.json-		0.138123,

@yarikoptic
Copy link
Member

FWIW: woohoo -- based on checksum, that test scout we have is from http://datasets.datalad.org/?dir=/dicoms/dartmouth-phantoms/bids_test4-20161014/phantom-1/anat-scout_ses-localizer , for which we also have e.g. http://datasets.datalad.org/?dir=/dicoms/dartmouth-phantoms/bids_test4-20161014/phantom-1/anat-scout_ses-localizer_MPR_cor which has 3 dicoms which are similarly of the different orientations. So you might use those (instead of the ones I recommended) for consistency. ... those 3 files gzip into 100k, so in principle we could have carried their copy, but I would prefer not to as long as we can fetch them.

@bpinsard bpinsard force-pushed the multiple_converted_file_sliceorientationpatient branch from eb60770 to 418601f Compare May 27, 2022 01:22
)))

is_multiorient = len(iops) > 1

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

forgot to call update_mutliorient_name below?

would need also a unittest of that function (seems should be doable without files) and ideally some test files to ensure that use here works out

@yarikoptic
Copy link
Member

had a look and left "critical" observation(s). Will convert to draft for now - please undraft/ping whenever it is ready for re-review.

@yarikoptic yarikoptic marked this pull request as draft February 17, 2023 14:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants